#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
   File Name：    test_demo_api.py
   Description :  restful接口测试 demo
-------------------------------------------------
"""
import time
from typing import Any, Dict

import allure
import requests

from apitesting.src.models.request_param import PrePareRequestModel
from apitesting.src.utils.asserts.assert_handler import Asserter
from apitesting.src.utils.asserts.validate_response import ValidateResponse
# from apitesting.models.test_suite import ResponseModel
from apitesting.common.util.allure_handler import allure_handler
from apitesting.common.util.logger import log
# from apitesting.models import PrePareRequestModel
# from apitesting.common.utils.data_formatter import json_format
from apitesting.src.base.requist_handler import ApiEngin
from apitesting.src.enums.request.engin import EnginType
from apitesting.src.enums.teardwon_type import TeardownType
from apitesting.data.data_manage.variable_extractor import var_extractor
# from apitesting.models.test_suite import ExtractVariable, TeardownItem
from apitesting.src.models.test_suite.test_suite_model import TestSuite
# from apitesting.models.test_suite import Assertion
from apitesting.src import models
from apitesting.src.models import (
    ExtractVariableModel,
    TeardownItem,
    StepModel
)

__all__ = ["ApiRequest", "api_test_request"]


class ApiTestRequest:

    def send_request(
            self,
            # 用例中单个步骤的数据,dict[config, test_steps]
            test_step: StepModel | Dict,
            *,
            request_engin: str = EnginType.requests,
    ):

        # 1、检查请求引擎是否合法
        if not EnginType.has_value(request_engin):
            raise ValueError(f"请求引擎 {request_engin} 不正确, 请选择正确的引擎")

        if not isinstance(test_step, StepModel):
            test_step_model = StepModel(**test_step)

        test_step_model = test_step
        # 对 config, test_step 等根模型先进行数据校验
        # test_case = TestSuite(**case_step_data)
        # case_config = tests.config
        # test_step_model = test_case.test_steps

        log.debug(f"# =========================执行用例 {test_step_model.name}======================== #")

        # log.debug(f"\n{test_step}")  # 打印用例数据
        # log.debug(f"用例数据\n{json_format(data=test_step_model.model_dump())}")
        # allure_step(step="步骤用例数据", var=test_step_model.model_dump())

        # 拼接请求地址
        test_step_model.url = "http://kdtx-test.itheima.net" + test_step_model.url

        # 2、获取解析后的请求数据

        # 3、前置处理操作
        # ============================================================================
        # ******************************** 前置处理
        # ============================================================================
        log.debug(f"🚀 开始执行前置处理")
        try:
            pass
        except Exception as e:
            log.error(f"前置处理失败: {e}")
            raise e

        # ================== 动态allure报告 ===============
        # self.allure_dyna_data(test_case)
        # ============================================================================
        # ******************************** 变量替换 ***********************************
        # ============================================================================
        if test_step_model.request.body is not None:
            test_step_model.request.body = var_extractor.vars_replace(template=test_step_model.request.body)

        # 4、整理请求参数、

        # 使用环境变量替换请求数据中的变量
        # ...

        # 5、发送请求

        # 数据校验
        # RequestParamModel(**case_step_data)

        if test_step_model.request.body is not None:
            test_step_model.request.headers = {
                "Content-Type": "application/json"
            }

        request_kwargs = PrePareRequestModel(
            headers=test_step_model.request.headers,
            method=test_step_model.method,
            url=test_step_model.url,
            json_data=test_step_model.request.body
        ).model_dump()

        # ===========================================================
        # ************ 🚀🚀🚀🚀🚀🚀发送请求 🚀🚀🚀🚀🚀*************
        # ===========================================================
        # 发送HTTP请求。
        response = self.send_http_request(request_kwargs=request_kwargs)
        # ===========================================================
        # ************************* 发后请求 *************************
        # ===========================================================
        # 处理HTTP响应。
        response_data: dict = self.process_response(response=response)

        # ===========================================================
        # ************************* 发后请求后处理 ********************
        # ===========================================================
        # 序列化响应数据（转换层json）
        # ...
        # 记录响应数据 (把相关信息到一个字典中

        # ======================== 后置处理 ========================== #
        self.post_process_test_step(
            response=response,
            teardown_item_list=test_step_model.request.teardown
        )

        # ======================== 校验响应（Validate Response） ========================== #
        """
        1、校验响应 HTTP 状态码：开启
        校验响应时，检查实际响应的状态码是否与接口文档例定义的状态码一致
        
        2、校验响应 Body 的数据结构：开启
        校验响应时，检查实际响应的 Body 的数据结构是否与接口文档例定义的数据结构一致
        
        3、校验响应 示例 的数据结构：开启
        校验响应时，检查实际响应的 Body 的数据结构是否与接口文档例定义的数据结构一致
        """

        ValidateResponse.validate_response(
            responses_model_list=test_step_model.response,
            response=response
        )
        # ************************* json models 校验 **************
        # self.validate_response_schema(tests=tests, response_data=response)

    @staticmethod
    def send_http_request(request_kwargs) -> Any:
        """
        发送HTTP请求。

        Args:
            request_kwargs (TestStepModel): 测试步骤模型。

        Returns:
            Any: HTTP响应对象。
        """
        # if test_step_model.method.lower() == "post":
        #     log.debug(f"🚀 开始发送 POST 请求body:\n {json_format(test_step_model.request.body)}")

        log.debug(f"🚀 开始发送请求")
        response = ApiEngin.request(**request_kwargs)
        log.debug(f"✅ 发送请求成功")
        log.debug(f"{type(response)}")

        return response

    @staticmethod
    def process_response(response: Any) -> Dict[str, Any]:
        """
        处理HTTP响应。

        Args:
            response (Any): HTTP响应对象。

        Returns:
            dict: 处理后的响应数据。
        """
        response_data = {}
        res_headers = dict(response.headers)
        res_content_type = res_headers.get('Content-Type')
        try:
            if res_content_type and 'application/json' in res_content_type:
                json_data = response.json()
            else:
                json_data = {}
        except Exception as e:
            Log.error(f"响应数据解析失败: {e}")
            raise e

        response_data['url'] = str(response.url)
        response_data['status_code'] = int(response.status_code)
        response_data['elapsed'] = response.elapsed.microseconds / 1000.0
        response_data['headers'] = res_headers
        response_data['cookies'] = dict(response.cookies)
        response_data['json'] = json_data
        response_data['content'] = response.content
        response_data['text'] = response.text

        return response_data

    @staticmethod
    def post_process_test_step(response: requests.models.Response, teardown_item_list: list[TeardownItem]):
        """
        执行测试步骤的后处理操作。

        参数:
        - response: HTTP响应对象
        - teardown_item_list: 后置脚本项列表

        返回值:
        无
        """

        Log.debug("🚀 开始处理后置脚本")
        assertion_results = []  # 存放所有断言结果
        try:
            if teardown_item_list:
                for teardown_item in teardown_item_list:
                    if not isinstance(teardown_item, TeardownItem):
                        Log.error(f"后置脚本项类型不正确: {type(teardown_item)}")
                        continue
                    # 获取单个后置脚本 此时这里的item还是 TeardownItem 类型root = ...
                    for key, value in teardown_item.root.items():  # .model_dump()

                        # ===============提取变量============== #
                        if key == TeardownType.EXTRACT or isinstance(value, ExtractVariableModel):
                            Log.info(f"后置 teardown_extract: {value}")
                            var_extractor.teardown_var_extract(
                                response=response.json(),
                                extract_model=value
                            )

                        # ===============响应断言============== #
                        elif key == TeardownType.ASSERT or isinstance(value, models.Assertion):
                            Log.info(f"后置 teardown_assert: {value}")
                            assertion_results += Asserter.exec_asserter(
                                assertion=value,  # teardown_item.root.get("assert")
                                response=response
                            )
                        elif key == TeardownType.WAIT_TIME:
                            Log.info(f"后置 teardown_wait_time: {value}s")
                            time.sleep(value)

                        # ===============目前都不支持============== #
                        else:
                            Log.debug(f"后置脚本类型 {key} 不正确, 断言类型为: {type(value)}")
                            Log.error(
                                f"后置脚本类型 {key} 暂不支持。请检查是否配置正确.目前支持如下: \n{TeardownType.get_all_values()}"
                            )
            else:
                Log.debug("后置脚本列表为空，无需处理")
        except Exception as e:
            Log.error(f"☹️后置处理失败: {e}", exc_info=True)
            raise e
        else:
            # 所有断言判断结束，输出结果
            if assertion_results:
                Log.debug("===================== 断言结果 ======================================================")
                for index, result_info in enumerate(assertion_results, 1):
                    Log.info(f"{index}. {result_info}")
                Log.debug("====================================================================================")

    @staticmethod
    def allure_dyna_data(test_case: TestSuite) -> None:

        id_name = test_case.test_steps.case_id
        tag = test_case.config.module
        epic = test_case.config.allure.epic
        feature = test_case.config.allure.feature
        story = test_case.config.allure.story
        title = test_case.test_steps.name
        description = test_case.test_steps.desc
        link = test_case.test_steps.url
        allure_severity: bool = False

        # 动态参数
        # allure.dynamic.parameter()
        # 测试用例ID
        if id_name is not None:
            allure.dynamic.id(id_name)

        # 标签
        if tag is not None:
            allure.dynamic.tag(tag)

        # 项目名称
        if epic is not None:
            allure.dynamic.epic(epic)

        # 功能模块
        if feature is None:
            allure.dynamic.feature(feature)

        # 需求模块（场景）
        if story is not None:
            allure.dynamic.story(story)

        # 测试用例名称
        if title is not None:
            allure.dynamic.title(title)

        # 测试用例描述
        if description is not None:
            allure.dynamic.description(description)

        # 测试用例链接
        if link is not None:
            allure.dynamic.link(link)

        if allure_severity is not None:
            allure.dynamic.severity(allure_severity)

    @staticmethod
    def allure_request_up(parsed_data: dict) -> None:
        allure_step(
            '请求数据',
            {
                'env': parsed_data['env'],
                'module': parsed_data['module'],
                'name': parsed_data['name'],
                'case_id': parsed_data['case_id'],
                'description': parsed_data['description'],
                'method': parsed_data['method'],
                'url': parsed_data['url'],
                'params': parsed_data['params'],
                'headers': parsed_data['headers'],
                'body_type': parsed_data['body_type'],
                'body': parsed_data['body'],
                'files': parsed_data['files_no_parse'],
            },
        )

    @staticmethod
    def allure_request_teardown(teardown_log: dict) -> None:
        allure_step('请求后置', teardown_log)


api_test_request = ApiTestRequest()
ApiRequest = ApiTestRequest()
